# -*- coding: utf-8 -*-
# @file    : action.py
# @Date    : 2020/10/21
# @Author  :
# @Version : 1.0.0

import re
import asyncio
from asyncio import CancelledError

from .device import Device
from .exception import StopGroup, BreakGroup, SkipAction


class Action(object):
    """
    动作包装对象

    """

    def __init__(self, dev, handle, expr):
        self.dev = dev
        self.handle = handle
        self.expr = expr

    def exec(self):
        return self.handle(self.expr)

    def __repr__(self):
        return "Action(dev={dev}, handle={handle}, expr={expr})".format(**self.__dict__)


class ActionGroup(object):
    """
    动作组

    Property:
        name:       名称
        repeat:     重复次数
        interval:   重复的间隔, 毫秒值
        state:      执行的状态,  True: 正在执行， False: 结束执行
        expr:       描述动作的指令表达式
        order:      解析后的指令序列
        device:     待操作的设备列表
        action:     指令序列包装后动作列表
        coroutine:  动作组对应的协程对象

    """
    ACTION_RE = re.compile(r"^\{(\w+)\[(\w+)::(.*?)\]\}")

    def __init__(self, name, repeat, interval, expr):
        assert isinstance(repeat, int) and (repeat > 0 or repeat == -1), "repeat 取值错误"
        assert isinstance(interval, int) and interval >= 0, "interval, 取值错误"
        self.name = name
        self.repeat = repeat  # 重复次数
        self.interval = interval
        self.running = False
        self.expr = expr
        self.order = self.parse(expr)
        assert len(self.order) > 0, "无可用的动作指令"

        self.device = {}
        self.action = []
        self.__load_order()

    @classmethod
    def parse(cls, expr: str):
        result = []
        lines = expr.replace("\r\n", "\n").split("\n")
        for line in lines:
            line = line.strip()
            if not line or line.startswith("#"):
                # 注释行
                continue
            has_find = cls.ACTION_RE.search(line)
            if not has_find:
                # 表达式语法错误
                raise ValueError(line)
            result.append(tuple(has_find.groups()))
        return result

    def get_coroutine(self):
        async def coroutine():
            print("动作组: %s, 任务开始" % self.name)
            try:
                count = 0
                while True:
                    count += 1
                    skip = 0

                    if self.repeat == 0:
                        break
                    elif self.repeat == -1:
                        pass
                    elif count > self.repeat:
                        break

                    print("动作组: %s, 执行第 %s 次" % (self.name, count))
                    for act in self.action:
                        if skip:
                            skip -= 1
                            continue
                        try:
                            result = act.exec()
                            if asyncio.iscoroutine(result):
                                # 要继续等待协程的执行结果
                                await result
                        except BreakGroup as e:
                            print("终止本次动作组: %s; %s" % (self.name, e))
                            break
                        except SkipAction as e:
                            print("跳过%s个动作: %s; %s" % (e.num, self.name, e))
                            skip += e.num

                    await asyncio.sleep(self.interval / 1000)
            except (CancelledError, StopGroup) as e:
                print("强制终止动作组: %s; %s" % (self.name, e))
            except Exception as e:
                print("动作组执行异常: %s; %s" % (self.name, e))
            finally:
                print("动作组: %s, 任务结束" % self.name)
                self.running = False

        return coroutine()

    def __call__(self):
        return self.get_coroutine()

    def __load_order(self):
        for order in self.order:
            action = self.__load_action(*order)
            self.action.append(action)

    def __load_action(self, dev_name, action_name, expr):
        dev = self.__load_device(dev_name)
        handle = getattr(dev, action_name, None)
        assert handle is not None and getattr(handle, '_action'), "未知动作 %s" % action_name
        return Action(dev, handle, expr)

    def __load_device(self, dev_name: str):
        if dev_name in self.device:
            return self.device[dev_name]
        assert dev_name in Device.supported_device, "未知设备 %s" % dev_name
        dev = Device.supported_device[dev_name]()
        self.device[dev_name] = dev
        return dev

    def __repr__(self):
        return "ActionGroup(name={name}, repeat={repeat}, interval={interval})".format(**self.__dict__)


class StoreActionGroup(ActionGroup):
    """
    入库专用动作组
    """

    def __init__(self, name, wh, bp):
        order = []
        sub_order = self.__parse_bp_points(bp)
        if wh:
            action_interval = wh['actionInterval']
            assert isinstance(action_interval, int) and action_interval >= 10, "仓库动作间隔设置太小"
            for wh_point in wh['points']:
                order.append(('mouse', 'move_to', "%d, %d" % tuple(wh_point)))
                order.append(('mouse', 'click', "left"))
                order.append(('system', 'sleep', str(action_interval)))
                order.extend(sub_order)
        else:
            order.extend(sub_order)

        expr = "\n".join(map(lambda o: "{%s[%s::%s]}" % o, order))
        super().__init__(name, 1, 0, expr)

    @staticmethod
    def __parse_bp_points(bp):
        action_interval = bp['actionInterval']
        assert isinstance(action_interval, int) and action_interval >= 10, "背包动作间隔设置太小"
        order = [('keyboard', 'press', '<ctrl>')]
        p1 = bp['points']['topLeft']
        p2 = bp['points']['bottomRight']
        row = bp['row']
        col = bp['col']
        dis_x = p2[0] - p1[0]
        dis_y = p2[1] - p1[1]
        cell_width = dis_x / col
        cell_height = dis_y / row
        cell_mid_width = cell_width / 2
        cell_mid_height = cell_height / 2

        for c in range(0, col):
            for r in range(0, row):
                cell_mid_point = (p1[0] + cell_width * c + cell_mid_width, p1[1] + cell_height * r + cell_mid_height)
                order.append(('mouse', 'move_to', "%d, %d" % cell_mid_point))
                order.append(('mouse', 'click', "left"))
                order.append(('system', 'sleep', str(action_interval)))

        order.append(('keyboard', 'release', '<ctrl>'))

        return order
